import textfsm
from tkinter import *
from tkinter.font import Font
import tkinter.messagebox
import time
from io import StringIO
import json
import re


class FSM:
    def __init__(self):
        # 字体等
        self.version = "Version 1.3"
        self.author = "曹占铎\nzandon@qq.com"
        self.font_size = 13

        # 最外层的容器
        self.master = Tk()
        self.master.geometry('960x540')     # 设置窗口大小
        self.master.title("ParseFSM")   # 设置标题
        self.master.iconbitmap("ParseFSM.ico")    # 设置图标
        self.font = Font(family="宋体", size=self.font_size)
        self.MainGUI()
        # 开启主循环，让窗口处于显示状态
        self.master.mainloop()

    def MainGUI(self):
        """创建上下两个容器，放置菜单栏和工具"""
        self.frame_main_01 = Frame(self.master, height=10)
        self.frame_main_01.pack(side=TOP, fill=BOTH, padx=0, pady=0)
        self.frame_main_02 = Frame(self.master)
        self.frame_main_02.pack(side=TOP, fill=BOTH, expand=True, padx=0, pady=0)
        """frame_main_01放置菜单栏"""
        self.ManuGUI()
        """frame_main_02放置FSM解析组件"""
        self.FsmGUI()

    def ManuGUI(self):
        # 主菜单
        self.main_menu = Menu(self.frame_main_01)
        # 向主菜单添加"视图"菜单，不添加分割线
        self.view_menu = Menu(self.main_menu, tearoff=False)
        # 新增"视图"菜单的菜单项，并使用accelerator设置菜单项的快捷键
        self.view_menu.add_command(label="普通模式", command=self.OrdinaryMode, accelerator="Alt+o")
        self.view_menu.add_command(label="暗黑模式", command=self.DarkMode, accelerator="Alt+d")
        self.view_menu.add_command(label="增大字体", command=self.SizeUp, accelerator="Alt+Up")
        self.view_menu.add_command(label="减小字体", command=self.SizeDown, accelerator="Alt+Down")
        # 向主菜单添加"关于"菜单，不添加分割线
        self.about_menu = Menu(self.main_menu, tearoff=False)
        # 新增"关于"菜单的菜单项，并使用accelerator设置菜单项的快捷键
        self.about_menu.add_command(label="版本信息", command=self.Version, accelerator="Alt+v")
        self.about_menu.add_command(label="关于作者", command=self.Author, accelerator="Alt+a")
        # 将"视图"和"关于"菜单串联到主菜单
        self.main_menu.add_cascade(label="视图", menu=self.view_menu)
        self.main_menu.add_cascade(label="关于", menu=self.about_menu)
        # 将主菜单设置在窗口上
        self.master.config(menu=self.main_menu)
        # 绑定键盘事件，按下键盘上的相应的键时都会触发执行函数
        self.master.bind("<Alt-o>", self.OrdinaryMode)
        self.master.bind("<Alt-d>", self.DarkMode)
        self.master.bind("<Alt-v>", self.Version)
        self.master.bind("<Alt-a>", self.Author)
        self.master.bind("<Alt-Up>", self.SizeUp)
        self.master.bind("<Alt-Down>", self.SizeDown)

    def FsmGUI(self):
        """左侧放一个frame容器, 用于放置待解析文本相关组件"""
        # 创建容器Frame
        frame_text = Frame(self.frame_main_02)
        frame_text.pack(side=LEFT, fill=BOTH, expand=True, padx=0, pady=0)
        frame_text.pack_propagate(False)    # 如果开启，父组件会自动调节尺寸以容纳所有子组件
        # 创建子容器
        frame_text_top = Frame(frame_text)
        frame_text_top.pack(side=TOP, fill=BOTH, expand=True, padx=0, pady=0)
        frame_text_down = Frame(frame_text, height=5)
        frame_text_down.pack(side=BOTTOM, fill=X, padx=0, pady=0)
        # 创建行号框
        self.text_text_line = Text(frame_text_top, wrap="none", width=4, font=self.font, bg="#F7F7F7", fg="#181818")
        # 创建文本框
        self.text_text = Text(frame_text_top, wrap="none", font=self.font)
        # 创建滚动条(纵向滚动条需要同时关联行号框和文本框，所以绑定了一个方法)
        self.scroll_text_x = Scrollbar(frame_text_down, orient=HORIZONTAL, bg="#3C3C3C", troughcolor="#000000")
        self.scroll_text_y = Scrollbar(frame_text_top, bg="#3C3C3C", troughcolor="#000000", command=self.ScrollCommandText)
        # 布局行号框，文本框和滚动条
        self.scroll_text_x.pack(side=BOTTOM, fill=X, padx=0, pady=0)
        self.scroll_text_y.pack(side=RIGHT, fill=Y, padx=0, pady=0)
        self.text_text_line.pack(side=LEFT, fill=Y, padx=0, pady=0)
        self.text_text.pack(side=LEFT, fill=BOTH, expand=True, padx=0, pady=0)
        # 文本框提示语
        self.text_text.insert(INSERT, "请输入待解析文本")
        # 行号框默认行号
        self.text_text_line.insert(INSERT, 1)
        # 滚动条关联文本框
        self.text_text.config(xscrollcommand=self.scroll_text_x.set)
        self.scroll_text_x.config(command=self.text_text.xview)
        self.text_text.config(yscrollcommand=self.scroll_text_y.set)
        # 事件绑定，纵向滚动条关联行号框和文本框
        self.text_text.bind("<MouseWheel>", self.WheelText)
        self.text_text_line.bind("<MouseWheel>", self.WheelText)
        self.scroll_text_y.bind("<MouseWheel>", self.WheelText)
        # 事件绑定,按下回车键、删除键或粘贴则重新计算行号
        self.text_text.bind(sequence="<Return>", func=self.SleepThenResetLineText)
        self.text_text.bind(sequence="<Control-v>", func=self.SleepThenResetLineText)
        self.text_text.bind(sequence="<BackSpace>", func=self.SleepThenResetLineText)
        # 事件绑定，行号框禁止编辑
        self.text_text_line.bind(sequence="<KeyPress>", func=self.DenyEdit)
        self.text_text_line.bind(sequence="<Control-v>", func=self.DenyEdit)

        """中间放一个frame容器, 用于放置TextFSM文本相关组件"""
        # 创建容器
        frame_fsm = Frame(self.frame_main_02)
        frame_fsm.pack(side=LEFT, fill=BOTH, expand=True, padx=0, pady=0)
        frame_fsm.pack_propagate(False)
        # 创建子容器
        frame_fsm_top = Frame(frame_fsm)
        frame_fsm_top.pack(side=TOP, fill=BOTH, expand=True, padx=0, pady=0)
        frame_fsm_down = Frame(frame_fsm, height=10)
        frame_fsm_down.pack(side=BOTTOM, fill=X, padx=0, pady=0)
        # 创建行号框,并禁止编辑
        self.text_fsm_line = Text(frame_fsm_top, wrap="none", width=4, font=self.font, bg="#F7F7F7", fg="#181818")
        # 创建文本框
        self.text_fsm = Text(frame_fsm_top, wrap="none", font=self.font)
        # 创建滚动条
        self.scroll_fsm_x = Scrollbar(frame_fsm_down, orient=HORIZONTAL, bg="#3C3C3C", troughcolor="#000000")
        self.scroll_fsm_y = Scrollbar(frame_fsm_top, bg="#3C3C3C", troughcolor="#000000", command=self.ScrollCommandFSM)
        # 布局行号框，文本框和滚动条
        self.scroll_fsm_x.pack(side=BOTTOM, fill=X, padx=0, pady=0)
        self.scroll_fsm_y.pack(side=RIGHT, fill=Y, padx=0, pady=0)
        self.text_fsm_line.pack(side=LEFT, fill=Y, padx=0, pady=0)
        self.text_fsm.pack(side=LEFT, fill=BOTH, expand=True, padx=0, pady=0)
        # 文本框提示语
        self.text_fsm.insert(INSERT, "请输入TextFSM模板内容，光标离开此区域将自动解析")
        # 行号框默认行号
        self.text_fsm_line.insert(INSERT, 1)
        # 滚动条关联文本框
        self.text_fsm.config(xscrollcommand=self.scroll_fsm_x.set)
        self.scroll_fsm_x.config(command=self.text_fsm.xview)
        self.text_fsm.config(yscrollcommand=self.scroll_fsm_y.set)
        # 事件绑定，纵向滚动条关联行号框和文本框
        self.text_fsm.bind("<MouseWheel>", self.WheelFSM)
        self.text_fsm_line.bind("<MouseWheel>", self.WheelFSM)
        self.scroll_fsm_y.bind("<MouseWheel>", self.WheelFSM)
        # 事件绑定,鼠标离开text_fsm则执行parse
        self.text_fsm.bind(sequence="<FocusOut>", func=self.parse)
        # 事件绑定,按下回车键、删除键或粘贴则重新计算行号
        self.text_fsm.bind(sequence="<Return>", func=self.SleepThenResetLineFSM)
        self.text_fsm.bind(sequence="<Control-v>", func=self.SleepThenResetLineFSM)
        self.text_fsm.bind(sequence="<BackSpace>", func=self.SleepThenResetLineFSM)
        # 事件绑定，禁止编辑
        self.text_fsm_line.bind(sequence="<KeyPress>", func=self.DenyEdit)
        self.text_fsm_line.bind(sequence="<Control-v>", func=self.DenyEdit)

        """右侧放一个frame容器,用于放置解析结果相关组件"""
        # 创建容器
        frame_result = Frame(self.frame_main_02)
        frame_result.pack(side=LEFT, fill=BOTH, expand=True, padx=0, pady=0)
        frame_result.pack_propagate(False)
        # 创建子容器
        frame_result_top = Frame(frame_result)
        frame_result_top.pack(side=TOP, fill=BOTH, expand=True, padx=0, pady=0)
        frame_result_down = Frame(frame_result, height=10)
        frame_result_down.pack(side=BOTTOM, fill=X, padx=0, pady=0)
        # 创建行号框,并禁止编辑
        self.text_result_line = Text(frame_result_top, wrap="none", width=4, font=self.font, bg="#F7F7F7", fg="#181818")
        # 创建文本框,并禁止编辑
        self.text_result = Text(frame_result_top, wrap="none", font=self.font)
        # 创建滚动条
        self.scroll_result_x = Scrollbar(frame_result_down, orient=HORIZONTAL, bg="#3C3C3C", troughcolor="#000000")
        self.scroll_result_y = Scrollbar(frame_result_top, bg="#3C3C3C", troughcolor="#000000", command=self.ScrollCommandResutl)
        # 布局行号框，文本框和滚动条
        self.scroll_result_x.pack(side=BOTTOM, fill=X, padx=0, pady=0)
        self.scroll_result_y.pack(side=RIGHT, fill=Y, padx=0, pady=0)
        self.text_result_line.pack(side=LEFT, fill=Y, padx=0, pady=0)
        self.text_result.pack(side=LEFT, fill=BOTH, expand=True, padx=0, pady=0)
        # 文本框提示语
        self.text_result.insert(INSERT, "尚未匹配到结果...")
        # 行号框默认行号
        self.text_result_line.insert(INSERT, 1)
        # 滚动条关联文本框
        self.text_result.config(xscrollcommand=self.scroll_result_x.set)
        self.scroll_result_x.config(command=self.text_result.xview)
        self.text_result.config(yscrollcommand=self.scroll_result_y.set)
        # 事件绑定，纵向滚动条关联行号框和文本框
        self.text_result.bind("<MouseWheel>", self.WheelResult)
        self.text_result_line.bind("<MouseWheel>", self.WheelResult)
        self.scroll_result_y.bind("<MouseWheel>", self.WheelResult)
        # 事件绑定，禁止编辑
        self.text_result_line.bind(sequence="<KeyPress>", func=self.DenyEdit)
        self.text_result_line.bind(sequence="<Control-v>", func=self.DenyEdit)
        self.text_result.bind(sequence="<KeyPress>", func=self.DenyEdit)
        self.text_result.bind(sequence="<Control-v>", func=self.DenyEdit)

    def WheelText(self, event):  # 鼠标滚轮动作
        # windows系统需要除以120
        self.text_text.yview_scroll(int(-1 * (event.delta / 120)), "units")
        self.text_text_line.yview_scroll(int(-1 * (event.delta / 120)), "units")
        return "break"

    def ScrollCommandText(self, *moveto):  # 在滚动条上点击、拖动等动作
        self.text_text.yview(*moveto)
        self.text_text_line.yview(*moveto)

    def WheelFSM(self, event):  # 鼠标滚轮动作
        # windows系统需要除以120
        self.text_fsm.yview_scroll(int(-1 * (event.delta / 120)), "units")
        self.text_fsm_line.yview_scroll(int(-1 * (event.delta / 120)), "units")
        return "break"

    def ScrollCommandFSM(self, *moveto):  # 在滚动条上点击、拖动等动作
        self.text_fsm.yview(*moveto)
        self.text_fsm_line.yview(*moveto)

    def WheelResult(self, event):  # 鼠标滚轮动作
        # windows系统需要除以120
        self.text_result.yview_scroll(int(-1 * (event.delta / 120)), "units")
        self.text_result_line.yview_scroll(int(-1 * (event.delta / 120)), "units")
        return "break"

    def ScrollCommandResutl(self, *moveto):  # 在滚动条上点击、拖动等动作
        self.text_result.yview(*moveto)
        self.text_result_line.yview(*moveto)

    def OrdinaryMode(self, *args):
        self.text_text.config(bg="#FFFFFF", fg="#181818")
        self.text_fsm.config(bg="#FFFFFF", fg="#181818")
        self.text_result.config(bg="#FFFFFF", fg="#181818")
        self.text_text_line.config(bg="#F7F7F7", fg="#181818")
        self.text_fsm_line.config(bg="#F7F7F7", fg="#181818")
        self.text_result_line.config(bg="#F7F7F7", fg="#181818")

    def DarkMode(self, *args):
        """暗黑模式"""
        # self.text_text.config(bg="#2B2B2B", fg="#999999")
        self.text_text.config(bg="#2B2B2B", fg="#00C957")
        self.text_fsm.config(bg="#2B2B2B", fg="#00C957")
        self.text_result.config(bg="#2B2B2B", fg="#00C957")
        self.text_text_line.config(bg="#313335", fg="#00C957")
        self.text_fsm_line.config(bg="#313335", fg="#00C957")
        self.text_result_line.config(bg="#313335", fg="#00C957")

    def Version(self, *args):
        tkinter.messagebox.showinfo(title=None, message="{}".format(self.version))

    def Author(self, *args):
        tkinter.messagebox.showinfo(title=None, message="{}".format(self.author))

    def SetSize(self, event):
        """根据滚轮滑动调解字体大小"""
        # 鼠标滚轮向上滚动,且当前字体大小小于20
        if event.delta == 120 and self.font_size < 20:
            self.font_size += 1
        # 鼠标滚轮向下滚动，且当前字体大小大于5
        if event.delta == -120 and self.font_size > 10:
            self.font_size -= 1
        # 重置字体
        self.font.config(size=self.font_size)

    def SizeUp(self, *args, **kwargs):
        if self.font_size < 20:
            self.font_size += 1
        # 重置字体
        self.font.config(size=self.font_size)

    def SizeDown(self, *args, **kwargs):
        if self.font_size > 10:
            self.font_size -= 1
        # 重置字体
        self.font.config(size=self.font_size)

    def SleepThenResetLineText(self, event):
        # 延迟一小段时间再获取Text中内容
        self.master.after(10, self.ResetLineText)

    def ResetLineText(self, *args, **kwargs):
        # 读取text文本
        text = self.text_text.get("1.0", "end")
        num = len(re.findall(r"\n", text))
        num = list(range(1, num + 1))
        num = [str(n) for n in num]
        lines = "\n".join(num)
        self.text_text_line.delete("1.0", "end")
        self.text_text_line.insert(index=1.0, chars=lines)

    def SleepThenResetLineFSM(self, event):
        # 延迟一小段时间再获取Text中内容
        self.master.after(10, self.ResetLineFSM)

    def ResetLineFSM(self, *args, **kwargs):
        # 读取text文本
        text = self.text_fsm.get("1.0", "end")
        num = len(re.findall(r"\n", text))
        num = list(range(1, num + 1))
        num = [str(n) for n in num]
        lines = "\n".join(num)
        self.text_fsm_line.delete("1.0", "end")
        self.text_fsm_line.insert(index=1.0, chars=lines)

    def DenyEdit(self, event):
        return "break"

    def parse(self, *args):
        # 读取text文本
        text = self.text_text.get("1.0", "end")
        # 读取textfsm模板
        fsm = self.text_fsm.get(index1=0.0, index2=END)
        # 解析
        if fsm != "\n":
            # 解析
            try:
                template = textfsm.TextFSM(StringIO(fsm))
                result = template.ParseTextToDicts(text)
                result = json.dumps(result, indent=4)
            except Exception as e:
                result = str(e)
            # 展示结果
            self.text_result.delete(index1=1.0, index2=END)
            self.text_result.insert(index=1.0, chars=result)
            # 重置行号
            num = len(re.findall(r"\n", result))
            num = list(range(1, num + 1))
            num = [str(n) for n in num]
            lines = "\n".join(num)
            self.text_result_line.delete("1.0", "end")
            self.text_result_line.insert(index=1.0, chars=lines)


if __name__ == '__main__':
    FSM()